package cn.goldgrid.web;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.beanutils.converters.SqlDateConverter;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.log4j.Logger;

import cn.goldgrid.ex.ActionException;
import cn.goldgrid.utils.Multimedia;
import cn.goldgrid.utils.MultipartRequest;
import cn.goldgrid.utils.RequestUtils;
import cn.utils.CryptUtils;
import cn.utils.ResourceUtils;

/**
 * 璇锋眰涓婁笅鏂�
 * @author Winter Lau
 * @date 2010-1-13 涓嬪崍04:18:00
 */
public class RequestContext {
	
	private final static Logger logger = Logger.getLogger(RequestContext.class); 

	private final static int MAX_FILE_SIZE = 10*1024*1024; 
	
	public static String encoding = "UTF-8";
	
	private final static ThreadLocal<RequestContext> contexts = new ThreadLocal<RequestContext>();
	private final static boolean isResin;
	
	private final static String upload_tmp_path;
	private final static String TEMP_UPLOAD_PATH_ATTR_NAME = "$TEMP_UPLOAD_PATH$";

	private static String webroot = null;
	
	private ServletContext context;
	private HttpSession session;
	private HttpServletRequest request;
	private HttpServletResponse response;
	private Map<String, Cookie> cookies;

	static {
		webroot = getWebrootPath();
		isResin = _CheckResinVersion();
		//涓婁紶鐨勪复鏃剁洰褰�
		upload_tmp_path = webroot + "WEB-INF" + File.separator + "tmp" + File.separator;
		try {
			FileUtils.forceMkdir(new File(upload_tmp_path));
		} catch (IOException excp) {}
		
		//BeanUtils瀵规椂闂磋浆鎹㈢殑鍒濆鍖栬缃�
		ConvertUtils.register(new SqlDateConverter(null), java.sql.Date.class);
		ConvertUtils.register(new Converter(){
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-M-d");
			SimpleDateFormat sdf_time = new SimpleDateFormat("yyyy-M-d H:m");
			@SuppressWarnings("rawtypes")
			public Object convert(Class type, Object value) {
				if(value == null) return null;
		        if (value instanceof Date) return (value);
		        try {
		            return sdf_time.parse(value.toString());
		        } catch (ParseException e) {
		            try {
						return sdf.parse(value.toString());
					} catch (ParseException e1) {
						return null;
					}
		        }
			}}, java.util.Date.class);
	}
	
	private final static String getWebrootPath() {
		String root = RequestContext.class.getResource("/").getFile();
		try {
			root = new File(root).getParentFile().getParentFile().getCanonicalPath();
			root += File.separator;
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		return root;
	}
	
	/**
	 * 鍒濆鍖栬姹備笂涓嬫枃
	 * @param ctx
	 * @param req
	 * @param res
	 */
	public static RequestContext begin(ServletContext ctx, HttpServletRequest req, HttpServletResponse res) {
		RequestContext rc = new RequestContext();
		rc.context = ctx;
		rc.request = _AutoUploadRequest(_AutoEncodingRequest(req));
		rc.response = res;
		rc.response.setCharacterEncoding(encoding);
		rc.session = req.getSession(false);
		rc.cookies = new HashMap<String, Cookie>();
		
		Cookie[] cookies = req.getCookies();
		if(cookies != null)
			for(Cookie ck : cookies) {
				rc.cookies.put(ck.getName(), ck);
			}
		contexts.set(rc);
		return rc;
	}

	/**
	 * 杩斿洖Web搴旂敤鐨勮矾寰�
	 * @return
	 */
	public static String root() { 
		return webroot; 
	}
	
	/**
	 * 鑾峰彇褰撳墠璇锋眰鐨勪笂涓嬫枃
	 * @return
	 */
	public static RequestContext get(){
		return contexts.get();
	}
	
	/**
	 * 缁撴潫璇锋眰 澶勭悊
	 */
	public void end() {
		String tmpPath = (String)request.getAttribute(TEMP_UPLOAD_PATH_ATTR_NAME);
		if(tmpPath != null){
			try {
				FileUtils.deleteDirectory(new File(tmpPath));
			} catch (IOException e) {
				logger.fatal("Failed to cleanup upload directory: " + tmpPath, e);
			}
		}
		this.context = null;
		this.request = null;
		this.response = null;
		this.session = null;
		this.cookies = null;
		contexts.remove();
	}
	
	/**
	 * 鑾峰彇璇锋眰locale
	 * @return
	 */
	public Locale locale(){ return request.getLocale(); }
	
	/**
	 * 璁剧疆鍏抽棴缂撳瓨
	 * @return
	 */
	public void closeCache(){
        header("Pragma","No-cache");
        header("Cache-Control","no-cache");
        header("Expires", 0L);
	}
	
	/**
	 * 鑷姩缂栫爜澶勭悊
	 * @param req
	 * @return
	 */
	private static HttpServletRequest _AutoEncodingRequest(HttpServletRequest req) {
		if(req instanceof RequestProxy)
			return req;
		HttpServletRequest auto_encoding_req = req;
		if("POST".equalsIgnoreCase(req.getMethod())){
			try {
				auto_encoding_req.setCharacterEncoding(encoding);
			} catch (UnsupportedEncodingException e) {}
		}
		else if(!isResin)
			auto_encoding_req = new RequestProxy(req, encoding);
		
		return auto_encoding_req;
	}
	
	/**
	 * 鑷姩鏂囦欢涓婁紶璇锋眰鐨勫皝瑁�
	 * @param req
	 * @return
	 */
	private static HttpServletRequest _AutoUploadRequest(HttpServletRequest req){
		if(_IsMultipart(req)){
			String path = upload_tmp_path + RandomStringUtils.randomAlphanumeric(10);
			File dir = new File(path);
			if(!dir.exists() && !dir.isDirectory())	dir.mkdirs();
			try{
				req.setAttribute(TEMP_UPLOAD_PATH_ATTR_NAME,path);
				return new MultipartRequest(req, dir.getCanonicalPath(), MAX_FILE_SIZE, encoding);
			}catch(NullPointerException e){				
			}catch(IOException e){
				logger.fatal("Failed to save upload files into temp directory: " + path, e);
			}
		}
		return req;
	}
	
	
	/**
	 * 
	 * @return
	 */
	public long id() {
		return getParameter("id", 0L);
	}
	
	/**
	 * 鑾峰彇IP
	 * @return
	 */
	public String ip(){
		return RequestUtils.getRemoteAddr(request);
	}
	
	/**
	 * 鑾峰彇鍙傛暟鍊�
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public Enumeration<String> params() {
		return request.getParameterNames();
	}
	
	/**
	 * 鑾峰彇鍙傛暟鍊� ,涓虹┖璁剧疆榛樿鍊�
	 * @param name
	 * @param def_value
	 * @return
	 */
	public String getParameter(String name, String...def_value) {
		String v = request.getParameter(name);
		//鍒ゆ柇鏄惁get璺ㄥ煙璇锋眰
		if(v!=null&&isCrossDomain()){
			try {
				v = URLDecoder.decode(v, encoding);
			} catch (UnsupportedEncodingException e) {
			}
		}
		logger.info("璇锋眰鍦板潃["+uri()+"]鎺ユ敹鍙傛暟"+name+":"+v);
		return (v!=null)?v:((def_value.length>0)?def_value[0]:null);
	}
	/**
	 * 鑾峰彇鍙傛暟鍊� ,涓虹┖璁剧疆榛樿鍊�,涓旇浆鍖栨垚long
	 * @param name
	 * @param def_value
	 * @return
	 */
	public long getParameter(String name, long def_value) {
		return NumberUtils.toLong(getParameter(name), def_value);
	}
	/**
	 * 鑾峰彇鍙傛暟鍊� ,涓虹┖璁剧疆榛樿鍊�,涓旇浆鍖栨垚int
	 * @param name
	 * @param def_value
	 * @return
	 */
	public int getParameter(String name, int def_value) {
		return NumberUtils.toInt(getParameter(name), def_value);
	}
	/**
	 * 鑾峰彇鍙傛暟鍊� ,涓虹┖璁剧疆榛樿鍊�,涓旇浆鍖栨垚byte
	 * @param name
	 * @param def_value
	 * @return
	 */
	public byte getParameter(String name, byte def_value) {
		return (byte)NumberUtils.toInt(getParameter(name), def_value);
	}
	/**
	 * 鑾峰彇鍙傛暟鍊兼暟缁� 
	 * @param name
	 * @param def_value
	 * @return
	 */
	public String[] getParameterValues(String name) {
		return request.getParameterValues(name);
	}
	/**
	 * 鑾峰彇鍙傛暟鍊兼暟缁� ,杞寲鎴恖ong[]
	 * @param name
	 * @param def_value
	 * @return
	 */
	public long[] getParameterValues_Long(String name){
		String[] values = getParameterValues(name);
		if(values==null) return null;
		return (long[])ConvertUtils.convert(values, long.class);
	}
	/**
	 * 璇锋眰URL鍦板潃
	 * @return
	 */
	public String uri(){
		String uri = request.getContextPath();
		if(uri!=null&&uri.length()>1){
			return request.getRequestURI().substring(uri.length(),request.getRequestURI().length());
		}
		return request.getRequestURI();
	}
	/**
	 * 鑾峰彇鏍硅矾寰�
	 * @return
	 */
	public String getContextPath(){
		return request.getContextPath();
	}
	/**
	 * sendRedirect
	 * @return
	 */
	public void sendRedirect(String uri) throws IOException {
		response.sendRedirect(uri);
	}
	/**
	 * forward
	 * @param uri
	 * @throws ServletException
	 * @throws IOException
	 */
	public void forward(String uri) throws ServletException, IOException {
		RequestDispatcher rd = context.getRequestDispatcher(uri);
		rd.forward(request, response);
	}
	/**
	 * include
	 * @param uri
	 * @throws ServletException
	 * @throws IOException
	 */
	public void include(String uri) throws ServletException, IOException {
		RequestDispatcher rd = context.getRequestDispatcher(uri);
		rd.include(request, response);
	}
	/**
	 * 鏄惁鏄笂浼犺姹�
	 * @return
	 */
	public boolean isUpload(){
		return (request instanceof MultipartRequest);
	}
	
	/**
	 * 鑾峰彇涓婁紶鏂囦欢.
	 * @return
	 */
	public File file(String fieldName) {
		if(request instanceof com.oreilly.servlet.MultipartRequest)
			return ((MultipartRequest)request).getFile(fieldName);
		return null;
	}
	/**
	 * 鑾峰彇涓婁紶鍥剧墖
	 * @return
	 */
	public File image(String fieldname) {
		File imgFile = file(fieldname);	
		return (imgFile!=null&&Multimedia.isImageFile(imgFile.getName()))?imgFile:null;
	}
	/**
	 * 鍒ゆ柇鏄惁涓烘悳绱㈠紩鎿�
	 * @return
	 */
	public boolean isRobot(){
		return RequestUtils.isRobot(request);
	}
	/**
	 * 鑾峰彇鍥介檯鍖� value
	 * @param bundle
	 * @param key
	 * @param args
	 * @return
	 */
	public ActionException fromResource(String bundle, String key, Object...args){
		String res = ResourceUtils.getStringForLocale(request.getLocale(), bundle, key, args);
		return new ActionException(res);
	}
	
	/**
	 * 鑾峰彇閿欒璧勬簮鎻愮ず.
	 * @param key
	 * @param args
	 * @return
	 */
	public ActionException error(String key, Object...args){		
		return fromResource("error", key, args);
	}
	
	/**
	 * 杈撳嚭淇℃伅鍒版祻瑙堝櫒
	 * @param msg
	 * @throws IOException
	 */
	public void print(Object msg) throws IOException {
		if(!encoding.equalsIgnoreCase(response.getCharacterEncoding())){
			response.setCharacterEncoding(encoding);
		}

		if(isCrossDomain()){
			msg = request.getParameter("jsoncallback")+"("+ msg+")";
		}
		logger.info("璇锋眰鍦板潃["+uri()+"]杩斿洖鏁版嵁"+msg);
		response.getWriter().print(msg);
	}
	
	/**
	 * 鏄惁jsonp璺ㄥ煙
	 * @return
	 */
	public boolean isCrossDomain(){
		if("GET".equalsIgnoreCase(request.getMethod())&&request.getParameter("jsoncallback")!=null){
			return true;
		}else return false;
	}

	public void output_json(String[] key, Object[] value) throws IOException {
		StringBuilder json = new StringBuilder("{");
		for(int i=0;i<key.length;i++){
			if(i>0)
				json.append(',');
			boolean isNum = value[i] instanceof Number ;
			json.append("\"");
			json.append(key[i]);
			json.append("\":");
			if(!isNum) json.append("\"");
			json.append(value[i]);
			if(!isNum) json.append("\"");
		}
		json.append("}");
		print(json.toString());
	}

	public void output_json(String key, Object value) throws IOException {
		output_json(new String[]{key}, new Object[]{value});
	}
	public void error(int code, String...msg) throws IOException {
		if(msg.length>0)
			response.sendError(code, msg[0]);
		else
			response.sendError(code);
	}
	
	public void forbidden() throws IOException { 
		error(HttpServletResponse.SC_FORBIDDEN,"褰撳墠鍦板潃鏃犳硶鏌ョ湅"); 
	}

	public void not_found() throws IOException { 
		error(HttpServletResponse.SC_NOT_FOUND,"璇锋眰鍦板潃鏈壘鍒�"); 
	}

	public ServletContext context() { return context; }
	public HttpSession session() { return session; }
	public HttpSession session(boolean create) { 
		return (session==null && create)?(session=request.getSession()):session; 
	}
	public Object sessionAttr(String attr) {
		HttpSession ssn = session();
		return (ssn!=null)?ssn.getAttribute(attr):null;
	}
	public HttpServletRequest request() { return request; }

	
	public HttpServletResponse response() { return response; }
	public Cookie cookie(String name) { return cookies.get(name); }
	public void cookie(String name, String value, int max_age, boolean all_sub_domain) {
		RequestUtils.setCookie(request, response, name, value, max_age, all_sub_domain);
	}
	public void deleteCookie(String name,boolean all_domain) { RequestUtils.deleteCookie(request, response, name, all_domain); }
	public String header(String name) { return request.getHeader(name); }
	public void header(String name, String value) { response.setHeader(name, value); }
	public void header(String name, int value) { response.setIntHeader(name, value); }
	public void header(String name, long value) { response.setDateHeader(name, value); }

	/**
	 * 灏咹TTP璇锋眰鍙傛暟鏄犲皠鍒癰ean瀵硅薄涓�
	 * @param req
	 * @param beanClass
	 * @return
	 * @throws Exception
	 */
	public <T> T form(Class<T> beanClass) {
		try{
			T bean = beanClass.newInstance();
			BeanUtils.populate(bean, request.getParameterMap());
			return bean;
		}catch(Exception e) {
			throw new ActionException(e.getMessage());
		}
	}

	
	/**
	 * 3.0 浠ヤ笂鐗堟湰鐨� Resin 鏃犻渶瀵筓RL鍙傛暟杩涜杞爜
	 * @return
	 */
	private final static boolean _CheckResinVersion() {
		try{
			Class<?> verClass = Class.forName("com.caucho.Version");
			String ver = (String)verClass.getDeclaredField("VERSION").get(verClass);
			String mainVer = ver.substring(0, ver.lastIndexOf('.'));
			
			return Float.parseFloat(mainVer) > 3.0;
		}catch(Throwable t) {}
		return false;
	}


	/**
	 * 鑷姩瑙ｇ爜
	 * @author liudong
	 */
	private static class RequestProxy extends HttpServletRequestWrapper {
		private String uri_encoding; 
		RequestProxy(HttpServletRequest request, String encoding){
			super(request);
			this.uri_encoding = encoding;
		}
		
		/**
		 * 閲嶈浇getParameter
		 */
		public String getParameter(String paramName) {
			String value = super.getParameter(paramName);
			return _DecodeParamValue(value);
		}

		/**
		 * 閲嶈浇getParameterMap
		 */
		@SuppressWarnings({ "unchecked", "rawtypes" })
		public Map<String, Object> getParameterMap() {
			Map params = super.getParameterMap();
			HashMap<String, Object> new_params = new HashMap<String, Object>();
			Iterator<String> iter = params.keySet().iterator();
			while(iter.hasNext()){
				String key = (String)iter.next();
				Object oValue = params.get(key);
				if(oValue.getClass().isArray()){
					String[] values = (String[])params.get(key);
					String[] new_values = new String[values.length];
					for(int i=0;i<values.length;i++)
						new_values[i] = _DecodeParamValue(values[i]);
					
					new_params.put(key, new_values);
				}
				else{
					String value = (String)params.get(key);
					String new_value = _DecodeParamValue(value);
					if(new_value!=null)
						new_params.put(key,new_value);
				}
			}
			return new_params;
		}

		/**
		 * 閲嶈浇getParameterValues
		 */
		public String[] getParameterValues(String arg0) {
			String[] values = super.getParameterValues(arg0);
			for(int i=0;values!=null&&i<values.length;i++)
				values[i] = _DecodeParamValue(values[i]);
			return values;
		}

		/**
		 * 鍙傛暟杞爜
		 * @param value
		 * @return
		 */
		private String _DecodeParamValue(String value){
			if (StringUtils.isBlank(value) || StringUtils.isBlank(uri_encoding)
					|| StringUtils.isNumeric(value))
				return value;		
			try{
				return new String(value.getBytes("8859_1"), uri_encoding);
			}catch(Exception e){}
			return value;
		}

	}
	
	private static boolean _IsMultipart(HttpServletRequest req) {
		return ((req.getContentType() != null) && (req.getContentType()
				.toLowerCase().startsWith("multipart")));
	}

	

	/**
	 * 鍔犲瘑
	 * @param value
	 * @return 
	 * @throws Exception 
	 */
	@SuppressWarnings("unused")
	private static String _Encrypt(String value) {
		byte[] data = CryptUtils.encrypt(value.getBytes(), E_KEY);
		try{
			return URLEncoder.encode(new String(Base64.encodeBase64(data)), encoding);
		}catch(UnsupportedEncodingException e){
			return null;
		}
	}

	/**
	 * 瑙ｅ瘑
	 * @param value
	 * @return
	 * @throws Exception 
	 */
	@SuppressWarnings("unused")
	private static String _Decrypt(String value) {
		try {
			value = URLDecoder.decode(value,encoding);
			if(StringUtils.isBlank(value)) return null;
			byte[] data = Base64.decodeBase64(value.getBytes());
			return new String(CryptUtils.decrypt(data, E_KEY));
		} catch (UnsupportedEncodingException excp) {
			return null;
		}
	}	


	@SuppressWarnings("unused")
	private final static int MAX_AGE = 86400 * 365;
	private final static byte[] E_KEY = new byte[]{'1','2','3','4','5','6','7','8'};
}